import 'dart:io';
import 'dart:isolate';

import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:hi_common/hi_common.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';

import 'package:path/path.dart';
import 'dart:math' as math;

/*
  下载文件类
  支持断点续传
 */
class Download {
  final int _isolateCount = 4;

  final int _dividerSize = 1024 * 1024 * 10;

  List<Isolate> _isolates = [];

  List<double> _progress;

  double get _totalProgress {
    double progress = 0;
    _progress.forEach((p) {
      progress += p;
    });

    return progress;
  }

  download(
    String downloadUrl, {
    ValueChanged<double> onProgressChanged,
    ValueChanged<String> onCompleted,
  }) async {
    _progress = List.generate(4, (index) => 0.0);

    var flag = await PermissionUtil.checkPermission(Permission.storage);

    if (!flag) {
      toast('没有权限，请前往设置中打开文件读写权限');
      return;
    }

    print('准备下载');

    var externalDir = await getExternalStorageDirectory();

    print('externalDir:${externalDir.path}');

    var downloadPath = join(externalDir.path, 'Download');

    var downloadDir = Directory(downloadPath);
    print('下载路径:${downloadDir.path}');

    bool hasExisted = await downloadDir.exists();

    if (!hasExisted) {
      await downloadDir.create();
    }

    var downloadFileName = basename(downloadUrl);

    var downloadFilePath = join(downloadPath, downloadFileName);

    var downloadFile = File(downloadFilePath);

    var isDownloadFileExisted = await downloadFile.exists();

    if (isDownloadFileExisted) {
      if (onCompleted != null) onCompleted(downloadFilePath);

      return;
    }

    var dio = Dio();

    var headerRes = await dio.head(downloadUrl);
    if (headerRes.statusCode != 200) {
      return headerRes;
    }

    var downloadUrlSize = int.parse(headerRes.headers[Headers.contentLengthHeader][0]);
    print('当前云端下载文件大小为$downloadUrlSize');

    final isolateCount = downloadUrlSize >= _dividerSize ? _isolateCount : 1;

    var averageSize = downloadUrlSize ~/ isolateCount;
    print('当前文件切割大小为$averageSize');

    var lastTaskSize = downloadUrlSize % isolateCount;
    print('当前文件最后一份需要额外下载大小为$lastTaskSize');

    for (int i = 0; i < isolateCount; i++) {
      var receivePort = ReceivePort();
      var sendPort = receivePort.sendPort;

      var startRange = i * averageSize;
      var endRange = startRange + averageSize - 1;
      if (i == isolateCount - 1) {
        endRange = startRange + averageSize + lastTaskSize - 1;
      }

      var message = DownloadIsolateMessage(
        startRange: startRange,
        endRange: endRange,
        saveFilePath: downloadFilePath,
        sendPort: sendPort,
        index: i,
        downloadUrl: downloadUrl,
        isolateCount: isolateCount,
      );

      Isolate.spawn<DownloadIsolateMessage>(downloadTask, message).then((isolate) {
        _isolates.add(isolate);

        receivePort.listen((message) async {
          if (message is DownloadIsolateCallback) {
            if (message.type == DownloadIsolateCallBackType.complete) {
              receivePort.close();

              isolate.kill();
              _isolates.remove(isolate);

              if (_isolates.length == 0) {
                await downloadFile.create();

                var downloadFileSink = downloadFile.openWrite(mode: FileMode.append);

                for (int i = 0; i < isolateCount; i++) {
                  var file = File(downloadFilePath + '-$i');
                  var sink = file.openRead();
                  await downloadFileSink.addStream(sink);
                  await file.delete();
                }

                await downloadFileSink.close();

                if (onCompleted != null) onCompleted(downloadFilePath);
              }
            } else {
              if (onProgressChanged != null) {
                _progress[i] = message.progress;

                onProgressChanged(math.min(_totalProgress, 100.0));
              }
            }
          }
        });

        sendPort.send(message);
      });
    }
  }

  cancel() {
    _isolates.forEach((isolate) {
      isolate.kill();
    });
  }
}

downloadTask(DownloadIsolateMessage message) async {
  final tag = '【download isolate-${message.index}】';

  print('$tag 开始工作:下载区间${message.startRange}-${message.endRange}');

  var sendPort = message.sendPort;
  var downloadUrl = message.downloadUrl;
  var saveFilePath = message.saveFilePath;
  var filename = saveFilePath + '-${message.index}';
  var file = File(filename);
  var isExists = await file.exists();
  if (!isExists) {
    await file.create();
  }

  var fileSize = await file.length();
  print('$tag 当前文件块大小:$fileSize');

  var startRange = message.startRange + fileSize;
  var totalSize = message.endRange - message.startRange;

  print('$tag 下载区间$startRange-${message.endRange}');
  print('$tag 当前需要下载文件块大小:${message.endRange - startRange}');

  if (message.endRange - startRange == -1) {
    sendPort.send(DownloadIsolateCallback(
      type: DownloadIsolateCallBackType.progress,
      progress: 100 / message.isolateCount,
    ));

    sendPort.send(DownloadIsolateCallback(
      type: DownloadIsolateCallBackType.complete,
    ));

    print('$tag完成');
    return;
  }

  var dio = Dio();
  Response<ResponseBody> res = await dio.get<ResponseBody>(
    downloadUrl,
    options: Options(
      responseType: ResponseType.stream,
      headers: {
        'Range': 'bytes=$startRange-${message.endRange}',
      },
    ),
  );

  var sink = file.openWrite(mode: FileMode.append);

  int count = fileSize;

  res.data.stream.listen((data) {
    count += data.length;

    double progress = count * 100 / totalSize / message.isolateCount;

    sendPort.send(DownloadIsolateCallback(
      type: DownloadIsolateCallBackType.progress,
      progress: progress,
    ));

    sink.add(data);
  }).onDone(() async {
    print('$tag完成');

    await sink.close();

    sendPort.send(DownloadIsolateCallback(type: DownloadIsolateCallBackType.complete));
  });
}

mergeFileTask() {}

class DownloadIsolateMessage {
  int startRange;
  int endRange;
  String saveFilePath;
  SendPort sendPort;
  String downloadUrl;
  int index;
  int isolateCount;

  DownloadIsolateMessage({
    @required this.startRange,
    this.endRange,
    @required this.saveFilePath,
    @required this.sendPort,
    @required this.index,
    @required this.downloadUrl,
    @required this.isolateCount,
  });
}

enum DownloadIsolateCallBackType {
  progress,
  complete,
}

class DownloadIsolateCallback {
  DownloadIsolateCallBackType type;
  double progress;

  DownloadIsolateCallback({
    @required this.type,
    this.progress,
  });
}
